--[[
Addon: GPS - Goblin Positioning System
Author: Coda
Realm: Varimathras
Changelog:
]]

local name = "GPS"
local G = getfenv(0)

local GPS = DongleStub("Dongle-1.0"):New(name)
local db, profile
local GPSPos = GPS:NewModule("GPSPos")
local Metrognome = DongleStub("MetrognomeNano-Beta0")
local Astrolabe = DongleStub("Astrolabe-0.4")
Astrolabe.UpdateTimer = 0.3

function GPSPos:Load(continent, zone, index)
    if not profile.positions then
        profile.positions = {}
    end
    if not profile.positions[continent] then
        profile.positions[continent] = {}
    end
    if not profile.positions[continent][zone] then
        profile.positions[continent][zone] = {}
    end
    if not index then
        return profile.positions[continent][zone]
    else 
        return profile.positions[continent][zone][index]    
    end
end

function GPSPos:Insert(continent, zone, x, y, desc)
    if not profile.positions then
        profile.positions = {}
    end
    if not profile.positions[continent] then
        profile.positions[continent] = {}
    end
    if not profile.positions[continent][zone] then
        profile.positions[continent][zone] = {}
    end
	table.insert(profile.positions[continent][zone], { X = x, Y = y, Desc = desc, Status = 0 })
end

function GPSPos:Update(index, continent, zone, x, y, desc)
    profile.positions[continent][zone][index] = { X = x, Y = y, Desc = desc, Status = 0 }
end

function GPSPos:Delete(index, continent, zone)
    table.remove(profile.positions[continent][zone], index)
end

-- Addon functions
function GPS:Initialize()
	SetCVar("scriptErrors", "1")

    -- Slash commands
	self.cmds = self:InitializeSlashCommand("Goblin Positioning System - v1.0.0Beta1", "GPS", "gps")
	self.cmds:RegisterSlashHandler("Shows/hide GPS interface", "^$", "UIToggle")

    -- Events
	self:RegisterEvent("PLAYER_ENTERING_WORLD")
	self:RegisterEvent("PLAYER_LEAVING_WORLD")
	self:RegisterEvent("ZONE_CHANGED_NEW_AREA")
	self:RegisterEvent("CLOSE_WORLD_MAP")
end

function GPS:Enable()
	db = self:InitializeDB("GPSDB")
	profile = db.profile

    self:Print("Goblin Positionning System activated.")

    Metrognome:Register("GPS", "UpdateMiniMap", self.UpdateMiniMap, 0.1)
    Metrognome:Register("GPS", "UpdatePosition", self.UpdatePosition, 0.1)



end

GPS.miniIcons = {}
GPS.icons = {}
GPS.lastMap = nil
GPS.maxMiniIcons = 75

function GPS:PLAYER_LEAVING_WORLD()
	if GPSFrame:IsShown() then
        self:Deselect()
		HideUIPanel(GPSFrame);
	end
    self:CleanMaps()
end

function GPS:ZONE_CHANGED_NEW_AREA()
	if GPSFrame:IsShown() then
        self:Deselect()
		HideUIPanel(GPSFrame);
	end
    self:CleanMaps()
end

function GPS:PLAYER_ENTERING_WORLD()
    local C, Z = GetCurrentMapContinent(), GetCurrentMapZone();
	if not C or C < 1 or not Z or not Astrolabe.ContinentList then
        GPSCoordsFrame:Hide()
        Metrognome:Stop('UpdatePosition')
        Metrognome:Stop('UpdateMiniMap')
    else 
        GPSCoordsFrame:Show()
        Metrognome:Start('UpdatePosition')
        Metrognome:Start('UpdateMiniMap')
    end

	if GPSFrame:IsShown() then
        self:Deselect()
		HideUIPanel(GPSFrame);
	end
    self:CleanMaps()
end

function GPS:CLOSE_WORLD_MAP()
    self:CleanMaps()
end

function GPS:CleanMaps()
    for _, zone in pairs(GPS.icons) do
        for _, i in ipairs(zone) do
            i:Hide()
        end
    end
    for _, zone in pairs(GPS.miniIcons) do
        for _, i in ipairs(zone) do
		    Astrolabe:RemoveIconFromMinimap(i)
        end
    end
    GPS.icons = {}
    GPS.miniIcons = {}
    GPS.lastMap = nil
end

-- Update minimap icons
function GPS:UpdateMiniMap()

    if WorldMapFrame:IsVisible() then
        return
    end

    local C, Z = GetCurrentMapContinent(), GetCurrentMapZone();
	if C < 1 or not Z or not Astrolabe.ContinentList then
	    return
    end

    local zone = Astrolabe.ContinentList[C][Z]
    if not zone then 
        return
    end

    if not GPS.lastMap then
        GPS.lastMap = zone
    elseif GPS.lastMap ~= zone then
        for _, z in pairs(GPS.miniIcons) do
            for _, i in ipairs(z) do
		        Astrolabe:RemoveIconFromMinimap(i)
            end
        end
        GPS.lastMap = zone
    end

    local positions = GPSPos:Load(C, GPS.lastMap)

    if GPS.miniIcons[zone] then
        for _, i in ipairs(GPS.miniIcons[zone]) do
            if not i:IsShown() then
                i:Show()
            end
        end
    else
        GPS.miniIcons[zone] = {}
    	for idx, position in ipairs(positions) do
    		local icon = GPS:CreateMiniMapIcon(position.Desc, position.X, position.Y)
		    Astrolabe:PlaceIconOnMinimap(icon, C, Z, position.X / 100, position.Y / 100)
    		icon:Show()
    		GPS.miniIcons[zone][#GPS.miniIcons[zone] + 1] = icon
    	end
    end

end

function GPS:CreateMiniMapIcon(label, x, y)
    local function MinimapIcon_OnEnter(self)
    	GameTooltip:SetOwner(self, "ANCHOR_CURSOR")
    	GameTooltip:SetText(self.label)
    	--[[
    	GameTooltip:AddLine(self.coord, 1, 1, 1)
        local dist,x,y = Astrolabe:GetDistanceToIcon(self)
    	GameTooltip:AddLine(("%s yards away"):format(math.floor(dist)), 1, 1 ,1)
    	GameTooltip:AddLine(self.zone, 0.7, 0.7, 0.7)
    	]]
    	GameTooltip:Show()
    end
    local function MinimapIcon_OnLeave(self)
    	GameTooltip:Hide()
    end
    local function MinimapIcon_OnUpdate(self, elapsed)

    	local edge = Astrolabe:IsIconOnEdge(self)
    	local dot = self.dot:IsShown()
    	local arrow = self.arrow:IsShown()

    	if edge and not arrow then
    		self.arrow:Show()
    		self.dot:Hide()
    		self.edge = true
    	elseif not edge and not dot then
    		self.dot:Show()
    		self.arrow:Hide()
    		self.edge = false
    	end
    end
    local halfpi = math.pi / 2
    local function MinimapIcon_UpdateArrow(self, elapsed)
    	local icon = self.parent
    	local angle = Astrolabe:GetDirectionToIcon(icon)
    	local x = .03875* math.cos(angle + halfpi) + 0.04875 
    	local y = .03875* math.sin(angle + halfpi) + 0.04875 
    	self:SetPosition(x,y,0)
    	self:SetFacing(angle)	
        local dist = Astrolabe:GetDistanceToIcon(icon)
        local size = (500 - dist) / 500
        if size < .45 then
            size = .45
        elseif size > 0.68 then
            size = 0.68
        end
	    self:SetModelScale(size)
    end

	-- Create a new icon with arrow
	-- icon.dot is the minimap dot texture
	-- icon.arrow is the model used on the edge of the map
	-- icon.label will contain the mouseover label
	-- icon.coord will contain the text of the coordinates
	local icon = CreateFrame("Button", nil, Minimap)
	icon:SetHeight(12)
	icon:SetWidth(12)

	icon.label = label
	icon.coord = string.format("%.1f, %.1f", x, y)

	local texture = icon:CreateTexture()
	texture:SetTexture("Interface\\Minimap\\ObjectIcons")
	texture:SetTexCoord(0.5, 0.75, 0, 0.25)
	texture:SetAllPoints()
	icon.dot = texture
	icon:SetScript("OnEnter", MinimapIcon_OnEnter)
	icon:SetScript("OnLeave", MinimapIcon_OnLeave)
	icon:SetScript("OnUpdate", MinimapIcon_OnUpdate)

	local model = CreateFrame("Model", nil, icon)
	model:SetHeight(140.8)
	model:SetWidth(140.8)
	model:SetPoint("CENTER", Minimap, "CENTER", 0, 0)
	model:SetModel("Interface\\Minimap\\Rotating-MinimapArrow.mdx")
	model.parent = icon
	icon.arrow = model
	model:SetScript("OnUpdate", MinimapIcon_UpdateArrow)
	model:Hide()

	return icon
end


function GPS:UpdatePosition()
	local C,Z,x,y = Astrolabe:GetCurrentPlayerPosition()
	if C and Z then
	    if not GPSCoordsFrame:IsShown() then
            GPSCoordsFrame:Show()
	    end
        GPSCoordsFrameContentText:SetText(string.format("%.1f - %.1f", x * 100.0, y * 100.0))
    else
        GPSCoordsFrameContentText:SetText("---")
	    if GPSCoordsFrame:IsShown() then
            GPSCoordsFrame:Hide()
	    end
    end
end

function GPS:SortTable(header)
    -- not implemented yet
end


function GPS:UpdateWorldMap(frame, elapsed)
    -- Called when WorldMap is displayed.
    -- It is called quite often, so we need to make sure
    -- no extra work is done.

	local C,Z,x,y = Astrolabe:GetCurrentPlayerPosition()

	local cX, cY = GetCursorPosition()
	local ceX, ceY = WorldMapFrame:GetCenter();
	local wmfw, wmfh = WorldMapButton:GetWidth(), WorldMapButton:GetHeight();
	
	cX = ( ( ( cX / WorldMapFrame:GetScale() ) - ( ceX - wmfw / 2 ) ) / wmfw + 22/10000 );
	cY = ( ( ( ( ceY + wmfh / 2 ) - ( cY / WorldMapFrame:GetScale() ) ) / wmfh ) - 262/10000 );
	
	if not Z then
		GPSWorldFramePlayer:SetText("You are here: ---")
	else
        GPSWorldFramePlayer:SetText(string.format("You are here: %.1f, %.1f", x*100, y*100))
	end
	
	if not cX or not cY then
		GPSWorldFrameCursor:SetText("Cursor: ---")
	else
        GPSWorldFrameCursor:SetText(string.format("Cursor: %.1f, %.1f", cX*100, cY*100))
	end
	
	self:WorldMapUpdateIcons(elapsed)
end



function GPS:CreateWorldMapIcon(label, x, y)
	
	-- Create a new icon with arrow
	-- icon.dot is the minimap dot texture
	-- icon.label will contain the mouseover label
	-- icon.coord will contain the text of the coordinates
	local icon = CreateFrame("Button", nil, GPSWorldFrame)
	icon:SetHeight(12)
	icon:SetWidth(12)

	icon.label = label
	icon.coord = string.format("%s, %s", x, y)

    local WorldMapIcon_OnEnter = function(icon)
		local x, y = icon:GetCenter();
		local parentX, parentY = icon:GetParent():GetCenter();
		if ( x > parentX ) then
			WorldMapTooltip:SetOwner(icon, "ANCHOR_LEFT");
		else
			WorldMapTooltip:SetOwner(icon, "ANCHOR_RIGHT");
		end
		WorldMapTooltip:SetText(icon.label);
		WorldMapTooltip:Show();
    end

    local WorldMapIcon_OnLeave = function(icon)
		WorldMapTooltip:Hide();
    end

	local texture = icon:CreateTexture()
	texture:SetTexture("Interface\\Minimap\\ObjectIcons")
	texture:SetTexCoord(0.5, 0.75, 0, 0.25)
	texture:SetAllPoints()
	icon.dot = texture
	icon:SetScript("OnEnter", WorldMapIcon_OnEnter)
	icon:SetScript("OnLeave", WorldMapIcon_OnLeave)
	return icon
end


function GPS:WorldMapUpdateIcons(elapsed)

	local C, Z = GetCurrentMapContinent(), GetCurrentMapZone();
	if C < 1 or not Astrolabe.ContinentList then
	    -- battleground or entire world map
	    return
    end

    local zone = Astrolabe.ContinentList[C][Z]
    if not zone then
        return
    end

    if not GPS.lastMap then
        GPS.lastMap = zone
    elseif GPS.lastMap ~= zone then
        for _, z in pairs(GPS.icons) do
            for _, i in ipairs(z) do
                i:Hide()
            end
        end
        GPS.lastMap = zone
    end

    local positions = GPSPos:Load(C, zone)

    if GPS.icons[zone] then
        for _, i in ipairs(GPS.icons[zone]) do
            if not i:IsShown() then
                i:Show()
            end
        end
    else
        GPS.icons[zone] = {}
    	for idx, position in ipairs(positions) do
    		local icon = self:CreateWorldMapIcon(position.Desc, position.X, position.Y)
    		local x,y = Astrolabe:PlaceIconOnWorldMap(WorldMapDetailFrame, icon, C, Z, position.X / 100, position.Y / 100)
    		icon:Show()
    		GPS.icons[zone][#GPS.icons[zone] + 1] = icon
    	end
    end
end



function GPS:CreatePositionsList()
    local lines = 12

    -- Resize column headers
    local sizes = { X = 40,  Y = 40, Desc = 198 }
	for f,width in pairs(sizes) do
        local frame = getglobal("GPSPositionsHeader_" .. f)
        frame:SetWidth(width);
        getglobal(frame:GetName() .. "Middle"):SetWidth(width - 9);
    end

	local function MakeRow(name, parent)
		local row = CreateFrame("CheckButton", name, parent, "GPSRowTemplate")
		row:SetCheckedTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
		row:GetHighlightTexture():SetBlendMode("ADD")
		return row
	end

	GPSPositionsFrame.rows = {}
	local rows = GPSPositionsFrame.rows
	for i=1,lines do
		rows[i] = MakeRow("GPSPositionsRow_" .. i, GPSPositionsFrame)
		rows[i].idx = i
	end

	rows[1]:SetPoint("TOPLEFT", GPSPositionsFrame, "TOPLEFT", 0, -24)
	for i=2,lines do
		 rows[i]:SetPoint("TOPLEFT", rows[i-1], "BOTTOMLEFT", 0, 0)
	end

	local postClick = function(row)
        GPS:EditSelected(row)
        GPS_ScrollFrameUpdate()
	end

	for i,frame in ipairs(GPSPositionsFrame.rows) do
		frame:SetScript("PostClick", postClick)
	end

    GPSPositionsScrollFrame.entries = rows

end

function GPS_ScrollFrameUpdate()
    SetMapToCurrentZone()
    local lines = 12
	local C,Z,x,y = Astrolabe:GetCurrentPlayerPosition()
	local positions
	if not Z then return end

    local zone = Astrolabe.ContinentList[C][Z]
    positions = GPSPos:Load(C, zone)

	local idx, button
    FauxScrollFrame_Update(GPSPositionsScrollFrame, #positions, lines, 20)

	for i=1,lines do

		idx = FauxScrollFrame_GetOffset(GPSPositionsScrollFrame) + i
		row = GPSPositionsFrame.rows[i]

		local XColumn    = getglobal(row:GetName().."_X")
		local YColumn    = getglobal(row:GetName().."_Y")
		local DescColumn = getglobal(row:GetName().."_Desc")

		if idx <= #positions then

			local position = positions[idx]

			local color
			if position.Status == 0 then
			    color = "cea902";
			else 
			    color = "00ff00";
			end
			XColumn:SetText(position.X)
			YColumn:SetText(position.Y)
			DescColumn:SetText("|cff" .. color .. position.Desc .. "|r")
			row:Show()

			if GPSPositionsFrame.selected == idx then
				row:SetChecked(true)
			else
				row:SetChecked(false)
			end
		else
			XColumn:SetText("")
			YColumn:SetText("")
			DescColumn:SetText("")
			row:SetChecked(false)
			row:Hide()
		end
    end
end

function GPS:SavePosition()
    local frame = getglobal("GPSPositionsFrame")

    local C, Z = Astrolabe:GetCurrentPlayerPosition()
    local selected = GPSPositionsFrame.selected
    local zone = Astrolabe.ContinentList[C][Z]
    if selected then
        local values = self:CheckInput()
        GPSPos:Update(selected, C, zone, values[1], values[2], values[3])
        GPS_ScrollFrameUpdate()
    	self:ClearFocus()
        self:EnableButtons()
    else
        local values = self:CheckInput()
        GPSPos:Insert(C, zone, values[1], values[2], values[3])
        self:Deselect()
    end
    self:CleanMaps()
end

function GPS:DeletePosition()
	local C,Z,x,y = Astrolabe:GetCurrentPlayerPosition()
	if not Z then return end

    local selected = GPSPositionsFrame.selected
    GPSPos:Delete(selected, C, Astrolabe.ContinentList[C][Z])
    self:Deselect()
	FauxScrollFrame_SetOffset(GPSPositionsScrollFrame, 0);
    self:CleanMaps()
end

function GPS:ClearFocus()
    GPSPositionsEdit_X:ClearFocus()
    GPSPositionsEdit_Y:ClearFocus()
    GPSPositionsEdit_Desc:ClearFocus()
end

function GPS:Deselect()
    GPSPositionsFrame.selected = nil
    GPSPositionsEdit_X:SetText("")
    GPSPositionsEdit_Y:SetText("")
    GPSPositionsEdit_Desc:SetText("")
    self:ClearFocus()
    GPS_ScrollFrameUpdate()
    self:EnableButtons()
end

function GPS:EnableButtons()
    local selected = GPSPositionsFrame.selected
	if selected then
		GPSPositionsButton_Save:Enable()
		GPSPositionsButton_Delete:Enable()
	else
	    local values = self:CheckInput()
	    if values then
		    GPSPositionsButton_Save:Enable()
	    else
		    GPSPositionsButton_Save:Disable()
	    end
		GPSPositionsButton_Delete:Disable()
	end
end

function GPS:CheckInput()
    local X = string.gsub(GPSPositionsEdit_X:GetText(), ",", ".")
    local Y = string.gsub(GPSPositionsEdit_Y:GetText(), ",", ".")
    local Desc = GPSPositionsEdit_Desc:GetText()
    local matchnum = function(num)
        if string.match(num, "^%d%d?$") or
           string.match(num, "^%d%d?%.%d$") or
           string.match(num, "^%.%d$") then
            return true
        end 
    end
	if matchnum(X) and 
	   matchnum(Y) and
	   Desc ~= "" then
        return {X, Y, Desc}
    end
end

function GPS:EditHere()
	local C,Z,x,y = Astrolabe:GetCurrentPlayerPosition()
	if not Z then return end
    GPSPositionsFrame.selected = nil
    GPS_ScrollFrameUpdate()
    GPSPositionsEdit_X:SetText(format("%.1f", x * 100.0))
    GPSPositionsEdit_Y:SetText(format("%.1f", y * 100.0))
    if UnitExists("target") then
        GPSPositionsEdit_Desc:SetText(UnitName("target"))
    end
    self:EnableButtons()
end

function GPS:EditSelected(row)
	local C,Z,x,y = Astrolabe:GetCurrentPlayerPosition()
	if not Z then return end

    local selected = FauxScrollFrame_GetOffset(GPSPositionsScrollFrame) + row.idx

    if GPSPositionsFrame.selected == selected then
		PlaySound("igMainMenuOptionCheckBoxOff")
        self:Deselect()
        return
    else
	    PlaySound("igMainMenuOptionCheckBoxOn")
        self:ClearFocus()
    end

    GPSPositionsFrame.selected = selected
    local values = GPSPos:Load(C, Astrolabe.ContinentList[C][Z], selected)

    GPSPositionsEdit_X:SetText(values.X)
    GPSPositionsEdit_Y:SetText(values.Y)
    GPSPositionsEdit_Desc:SetText(values.Desc)

    self:EnableButtons()
end

function GPS:UIToggle()
	-- Show/Hide the interface
	if not GPSFrame:IsShown() then
        self:Deselect()
		ShowUIPanel(GPSFrame);
	else
        self:Deselect()
		HideUIPanel(GPSFrame);
	end
end

G[name] = GPS
